package com.xiaoman.httpremote.base;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiaoman.httpremote.Interceptor.HeaderInterceptor;
import com.xiaoman.httpremote.annotation.ExcludeRequestHeaders;
import com.xiaoman.httpremote.annotation.HttpRemoteUrl;
import com.xiaoman.httpremote.config.HeaderEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.BiFunction;

@Slf4j
public class HttpRemoteHandler<T> implements InvocationHandler {
    private Class<T> type;

    private ObjectMapper objectMapper;

    public HttpRemoteHandler(Class<T> type) {
        this.type = type;
        objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> declaringClass;
        HttpRemoteUrl annotation;
        if (Object.class.equals(declaringClass = method.getDeclaringClass())
                || null == (annotation = declaringClass.getAnnotation(HttpRemoteUrl.class))) {
            return method.invoke(this, args);
        }
//        String prefix = StringUtils.isBlank(annotation.value()) ? EnvContextHolder.getInstance().getEnv() : annotation.value();
//        String prefix = StringUtils.isBlank(annotation.value()) ? SpringContextHolder.getProperty("config.network") : annotation.value();
        String prefix = annotation.value();
        String[] headers = annotation.headers();
        Set<String> excluedeHeaders = new HashSet<>();
        ExcludeRequestHeaders classExcludeRequestHeaders = declaringClass.getAnnotation(ExcludeRequestHeaders.class);
        if (null != classExcludeRequestHeaders) excluedeHeaders.addAll(Arrays.asList(classExcludeRequestHeaders.value()));
        ExcludeRequestHeaders methodExcludeRequestHeaders = method.getAnnotation(ExcludeRequestHeaders.class);
        if (null != methodExcludeRequestHeaders) excluedeHeaders.addAll(Arrays.asList(methodExcludeRequestHeaders.value()));
        Map<String, String> headerMap = new HashMap<>();
        handleHeaders(headers, excluedeHeaders, headerMap, true);
        HttpRemoteUrl methodHttpRemoteUrlAnnotation = method.getAnnotation(HttpRemoteUrl.class);
        if (methodHttpRemoteUrlAnnotation != null) prefix = methodHttpRemoteUrlAnnotation.value();
        if (null != method.getAnnotation(GetMapping.class)) {
            return doGet(method, args, prefix, headerMap);
        }
        if (null != method.getAnnotation(PostMapping.class))
            return doPost(method, args, prefix, headerMap);
        return null;
    }

    private Object doPost(Method method, Object[] args, String prefix, Map<String, String> headerMap) throws JsonProcessingException {
        PostMapping postMapping = method.getAnnotation(PostMapping.class);
        String url = baseUriHandle(method, args, prefix, headerMap, postMapping.value(), postMapping.headers());
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Object body = null;
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation annotation = parameterAnnotations[i][j];
                if (RequestBody.class.equals(annotation.annotationType())) {
                    body = args[i];
                }
            }
        }
        Object finalBody = body;
        return dobaseRequest(method, headerMap, url, (u, h) -> {
//            try {
//                return HttpClientUtil.httpPost(u, JSON.toJSONString(finalBody), h);
//            } catch (IOException e) {
//            }
            //todo 发送post请求
            return null;
        });
    }

    private Object doGet(Method method, Object[] args, String prefix, Map<String, String> headerMap) throws JsonProcessingException {
        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        String url = baseUriHandle(method, args, prefix, headerMap, getMapping.value(), getMapping.headers());
        return dobaseRequest(method, headerMap, url, (u, h) -> {
//            try {
//                return HttpClientUtil.httpGet(u, h);
//            } catch (IOException e) {
//                throw new BusinessRuntimeException(StateCodeEnum.THERE_IS_CURRENTLY_NO_INTERNET_CONNECTION.getCode(),
//                        StateCodeEnum.THERE_IS_CURRENTLY_NO_INTERNET_CONNECTION.getMsg());
//            }
            //todo 发送get请求
            return null;
        });
    }

    private Object dobaseRequest(Method method, Map<String, String> headerMap, String url, BiFunction<String, Map<String, String>, String> biFunction) throws JsonProcessingException {
        String reponseBody;
        reponseBody = biFunction.apply(url, headerMap);
        String object = reponseBody;
//        try {
//            R<String> r = JSON.parseObject(reponseBody, new TypeReference<R<String>>() {
//            });
//            if (r == null)
//                return null;
//            if (r.getCode() != 200 && r.getCode() != 0)
//                throw new BusinessRuntimeException(r.getCode(), r.getMsg());
//            if (r.getCode() != 0)
//                object = r.getData();
//        } catch (Exception e) {
//            if (e instanceof BusinessRuntimeException)
//                throw new BusinessRuntimeException(((BusinessRuntimeException) e).getCode(), ((BusinessRuntimeException) e).getMsg());
//            object = reponseBody;
//        }
        if (object == null)
            return null;
        Type genericReturnType = method.getGenericReturnType();
        Class<?> returnType = method.getReturnType();
        if (genericReturnType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
            if (Collection.class.isAssignableFrom(returnType)) {
                return objectMapper.readValue(object, objectMapper.getTypeFactory().constructParametricType(Collection.class, getJavaType(parameterizedType.getActualTypeArguments()[0], method.getDeclaringClass())));
            }
            if (Map.class.isAssignableFrom(returnType)) {
                return objectMapper.readValue(object, Map.class);
            }
            return objectMapper.readValue(object, getJavaType(parameterizedType.getActualTypeArguments()[0], method.getDeclaringClass()));
        }
        return objectMapper.readValue(object, returnType);
    }

    private String baseUriHandle(Method method, Object[] args, String prefix, Map<String, String> headerMap, String[] values, String[] headers) {
        if (ArrayUtils.isEmpty(values))
            throw new RuntimeException("No accessible path!");
        String url = StringUtils.isNotBlank(prefix) ? prefix + (values[0].startsWith("/") ? values[0] : "/" + values[0]) : values[0];
        handleHeaders(headers, headerMap, false);
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Parameter[] parameters = method.getParameters();
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation annotation = parameterAnnotations[i][j];
                if (RequestParam.class.equals(annotation.annotationType())) {
                    RequestParam requestParam = (RequestParam) annotation;
                    String paramName = StringUtils.isNotBlank(requestParam.value()) ? requestParam.value() : parameters[i].getName();
                    if (StringUtils.isNotBlank(stringBuilder.toString()))
                        stringBuilder.append("&");
                    stringBuilder.append(paramName).append("=").append(args[i]);
                }
                if (PathVariable.class.equals(annotation.annotationType())) {
                    PathVariable pathVariable = (PathVariable) annotation;
                    String paramName = pathVariable.value();
                    url = url.replaceAll("\\{" + paramName + "}", String.valueOf(args[i]));
                }
                if (RequestHeader.class.equals(annotation.annotationType())) {
                    RequestHeader requestHeader = (RequestHeader) annotation;
                    headerMap.put(StringUtils.isNotBlank(requestHeader.value()) ? requestHeader.value() : parameters[i].getName(), String.valueOf(args[i]));
                }
            }
        }
        String params = stringBuilder.toString();
        url = StringUtils.isNotBlank(params) ? url + "?" + params : url;
        return url;
    }
    private void handleHeaders(String[] headers, Map<String, String> headerMap, Boolean isRequest) {
        handleHeaders(headers, null, headerMap, isRequest);
    }
    private void handleHeaders(String[] headers, Set<String> excludeHeaders, Map<String, String> headerMap, Boolean isRequest) {
        if (!ArrayUtils.isEmpty(headers)) {
            for (String header : headers) {
                String[] split = header.split("=");
                headerMap.put(split[0], split[1]);
            }
        }
        RequestAttributes requestAttributes;
        if (isRequest && null != (requestAttributes = RequestContextHolder.getRequestAttributes())) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            for (HeaderEnum headerEnum : HeaderEnum.values()) {
                if (null != excludeHeaders && excludeHeaders.contains(headerEnum.header)) continue;
                if (StringUtils.isNotBlank(request.getHeader(headerEnum.header)))
                    headerMap.put(headerEnum.header, request.getHeader(headerEnum.header));
            }
        }
        for (HeaderInterceptor headerInterceptor : HeaderInterceptor.getHeaderInterceptors()) {
            Map<String, String> stringStringMap = headerInterceptor.addHeaders();
            for (Map.Entry<String, String> entry : stringStringMap.entrySet()) {
                if (null != excludeHeaders && excludeHeaders.contains(entry.getKey())) continue;
                headerMap.put(entry.getKey(), entry.getValue());
            }
        }
    }

    protected JavaType getJavaType(Type type, @org.springframework.lang.Nullable Class<?> contextClass) {
        return this.objectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass));
    }
}
